home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 April: Mac OS SDK / Dev.CD Apr 96 SDK / Dev.CD Apr 96 SDK1.toast / Development Kits (Disc 1) / OpenDoc / Documentation / Tech Notes & Articles / Recipes / UI & Events / Activation next >
Encoding:
Text File  |  1995-11-07  |  21.3 KB  |  489 lines  |  [TEXT/ttxt]

  1. OpenDoc™ Recipes
  2.  
  3. Activation
  4. By The OpenDoc Design Team
  5. November, 1995
  6.  
  7.  
  8. © 1993-1995  Apple Computer, Inc. All Rights Reserved.
  9. Apple, the Apple logo, and Macintosh are registered trademarks of Apple Computer, Inc.
  10. Mac and OpenDoc are trademarks of Apple Computer, Inc. 
  11.  
  12.  
  13. Foci and the Arbitrator
  14.  
  15. OpenDoc has a flexible and platform-neutral model of part activation, in which part editors request ownership of named foci from an arbitrator. Each session has an arbitrator, which can be obtained from the session using ODSession::GetArbitrator(). The foci are defined as ISO strings (in Foci.idl and its bindings Foci.xh and Foci.h), and the standard set includes:
  16.  
  17. const ODFocusType kODSelectionFocus = "Selection";    
  18. const ODFocusType kODMenuFocus = "Menu";    
  19. const ODFocusType kODKeyFocus = "Key";    
  20. const ODFocusType kODScrollingFocus = "Scrolling";    
  21. const ODFocusType kODModalFocus = "Modal";    
  22.  
  23.  
  24. These strings can be tokenized using ODSession::Tokenize(). The methods of ODArbitrator and ODFocusSet use the tokenized form of the focus names.
  25.  
  26. Foci are owned by frames. The active border is drawn around the frame with the selection focus, and Shift-clicks and Command-clicks are sent to this frame. Menu and keystroke events are sent to the frames with the menu focus and keystroke focus, respectively. Page Up and Page Down keystrokes are sent to the frame with the scrolling focus. See the Dialogs recipe for information about the modal focus.
  27.  
  28. There is no assumption that the same frame owns the selection, key and menu foci, even though this will often be the case. Furthermore, OpenDoc does not require that the selection focus be in an active window, even though this is usually what’s desired on Macintosh, unless the active window is a modeless dialog. See Window Activation and the Selection Focus, below.
  29.  
  30. Key Methods
  31.  
  32. From ODSession:
  33.  
  34. ODArbitrator GetArbitrator();
  35. ODTypeToken Tokenize(in ODType type);
  36.  
  37.  
  38. From ODArbitrator:
  39.  
  40. ODBoolean RequestFocusSet(in ODFocusSet focusSet,
  41.                            in ODFrame requestingFrame);
  42. ODBoolean RequestFocus(in ODTypeToken focus,
  43.                            in ODFrame requestingFrame);
  44. void RelinquishFocusSet(in ODFocusSet focusSet,
  45.                            in ODFrame relinquishingFrame);
  46. void RelinquishFocus(in ODTypeToken focus,
  47.                        in ODFrame relinquishingFrame);
  48. void TransferFocus(in ODTypeToken focus,
  49.                        in ODFrame transferringFrame,
  50.                                 in ODFrame newOwner);
  51. void TransferFocusSet(in ODFocusSet focusSet, 
  52.                            in ODFrame transferringFrame,
  53.                                     in ODFrame newOwner);
  54. ODFrame AcquireFocusOwner(in ODTypeToken focus);
  55. ODFocusSet CreateFocusSet();
  56.  
  57.  
  58. From ODPart:
  59.  
  60. ODBoolean BeginRelinquishFocus(in ODTypeToken focus,
  61.                                    in ODFrame ownerFrame,
  62.                                       in ODFrame proposedFrame);
  63. void CommitRelinquishFocus(in ODTypeToken focus,
  64.                                in ODFrame ownerFrame,
  65.                                   in ODFrame proposedFrame);
  66. void AbortRelinquishFocus(in ODTypeToken focus,
  67.                                in ODFrame ownerFrame,
  68.                                      in ODFrame proposedFrame);
  69. void FocusAcquired(in ODTypeToken focus,
  70.                        in ODFrame ownerFrame);
  71. void FocusLost(in ODTypeToken focus,
  72.                in ODFrame ownerFrame);
  73.  
  74.  
  75. Requesting a Focus or Focus Set
  76.  
  77. A part can request (for one of its frames) ownership of a single focus, or a set of foci (an ODFocusSet) using the ODArbitrator methods RequestFocus and RequestFocusSet(). RequestFocusSet() performs a “two-phase commit”. It first asks each current owner if it is willing to relinquish the focus (by calling Part::BeginRelinquishFocus()). If any focus owner is unwilling, the arbitrator aborts the request by calling each part’s AbortRelinquishFocus() method, and RequestFocusSet() returns kODFalse. If all focus owners are cooperative, the arbitrator calls each one’s CommitRelinquishFocus() method, and RequestFocusSet() returns kODTrue.
  78.  
  79. Note: OpenDoc does not activate parts. Parts activate themselves by requesting foci for frames. 
  80.  
  81. Transferring a Focus or Focus Set
  82.  
  83. A focus or set of foci can also be forcibly transferred from one frame to another, without negotiation.  ODArbitrator::TransferFocus() and TransferFocusSet() are used for this purpose. When a focus is transferred, the FocusAcquired()  method of the new owner's part is called, and the FocusLost method of the old owner's part is called, unless it is the one doing the transferring (the transferring frame is passed to TransferFocus() and TransferFocusSet()). 
  84.  
  85. Parts should generally request foci rather than transfer them. However, the recipe for handling modal dialogs makes use of TransferFocus. Since modal dialogs might be nested, the recipe consists of saving the current owner of the modal focus, requesting the modal focus, and then transferring it back to the saved owner when the dialog is dismissed. 
  86.  
  87. Note: Actually, if the nested modal dialogs do not themselves contain parts, the above is not strictly necessary, and the part displaying the modal dialog can simply relinquish the modal focus.
  88.  
  89. While parts generally don't transfer foci, they should be prepared to have certain foci forcibly removed. In this case the FocusLost() method will be called.  
  90.  
  91. Note: FocusAcquired() and FocusLost() are not called during the Request process. The requesting part knows it has acquired the foci because RequestFocus or RequestFocusSet returns kODTrue. The requesting part may be tempted to call its own FocusAcquired() method when RequestFocus() or RequestFocusSet() returns kODTrue, but it's  better to use separate private methods which can be called from both FocusAcquired() and when a request returns kODTrue.  This way there is a clear separation between the external and internal calls.
  92.  
  93. Similarly, a part may be tempted to call its own FocusLost() method from its CommitRelinquishFocus() method, but it would be better to keep the external and internal interfaces separate.
  94.  
  95. Keyboard Navigation
  96.  
  97. A containing part (such as a forms package) could provide keyboard navigation of embedded parts by transferring the keyboard and/or selection focus from one embedded part to another. The containing part would also have to set the doesPropagateEvents flag of embedded frames, so that it could handle Tab or arrow keys not consumed by the embedded parts. This recipe needs further research, but the containing part should probably request the focus for its own frame (so that an active embedded part can refuse to relinquish it if appropriate), and then transfer the focus to the next embedded frame.
  98.  
  99. The Scrolling Focus
  100.  
  101. PageDown and PageUp keys are sent to the part with the scrolling focus. If an embedded part with no scroll bars is active, the user should still be able to page through the document.  Typically, containers will have scroll bars, and embedded parts wont, although other configurations are possible. The following rules should be followed:
  102. An embedded part with the scolling focus should always relinquish it when asked to. 
  103. A root part in an active window should not relinquish the scrolling focus. 
  104.  
  105. Basic Frame Activation
  106.  
  107. Foci and Focus Sets
  108.  
  109. Most frames will require a focus set containing  the selection focus, keystroke focus and menu focus when the user clicks in the frame. But a frame with scroll bars might also need the scrolling focus.  And a frame for a modeless dialog may require the menu focus, but not the selection focus, so that the active border remains around the frame which opened the dialog, even though it's in an inactive window.
  110.  
  111. So it's best to preserve focus state on a per-frame basis, and this is one reason why those frame objects attached to the part info of ODFrame objects are so handy.
  112.  
  113. It's a good idea for each frame obect to pre-allocate an ODFocusSet  when it is initialized. The ODArbitrator method CreateFocusSet should be used to create the set. 
  114.  
  115. Note: In this example, the tokenized focus names are also cached in each frame. They could be cached in the part object, or even in a reference-counted C++ object stored in a library global shared by all part instances in a document. Per-context shared-library globals makes this practical. See the Shared Utility Windows recipe.
  116.  
  117. void ClockFrame::InitClockFrame(Environment* ev, 
  118.                                 ODSession* session,
  119.                                 ODFrame* frame, 
  120.                                 ClockPart* clockPart)
  121. {
  122.     fSession = session;
  123.     fArbitrator = fSession->GetArbitrator(ev);
  124.     fDispatcher = fSession->GetDispatcher(ev);
  125.     fWindowState = fSession->GetWindowState(ev);
  126.  
  127.     fFrame = frame;
  128.     fClockPart = clockPart;
  129.  
  130.     fFocusSet = fArbitrator->CreateFocusSet(ev);
  131.  
  132.     fSelectionFocus = fSession->Tokenize(ev, kODSelectionFocus);
  133.     fMenuFocus = fSession->Tokenize(ev, kODMenuFocus);
  134.     fKeyFocus = fSession->Tokenize(ev, kODKeyFocus);
  135.     fModalFocus = fSession->Tokenize(ev, kODModalFocus);
  136.     fShouldDeleteWindow = frame->IsRoot(ev);
  137.     fShouldHideOnSuspend = kODFalse;
  138. }
  139.  
  140. void ClockTimeFrame::InitClockTimeFrame(Environment* ev, 
  141.                                         ODSession* session,
  142.                                         ODFrame* frame, 
  143.                                         ClockPart* clockPart)
  144. {
  145.     this->InitClockFrame(ev, session, frame, clockPart);
  146.     fCrossCursor = **GetCursor(crossCursor);    
  147.  
  148.     fFocusSet->Add(ev, fKeyFocus);
  149.     fFocusSet->Add(ev, fMenuFocus);
  150.     fFocusSet->Add(ev, fSelectionFocus);
  151.  
  152.     if (frame->IsRoot(ev))
  153.         fNeedsFoci = kODTrue;
  154. }
  155.  
  156.  
  157. Requesting Foci
  158.  
  159. Frames are activated in a variety of circumstances - on mouse down, on mouse up, when a window is first opened, when a window is activated.
  160.  
  161. MouseUp Activation: When a frame is already selected, a mouse-down event in that frame will go to the containing frame so that it can allow the user to drag the selected frame. The embedded frame will receive a mouse-up event if the container decides a drag is not happening. Therefore frames should activate themselves if necessary on mouse-up.  For reasons explained in the Mouse Events recipe, they still need to activate on mouse down as well.
  162.  
  163. When the part editor receives an event, and decides it must activate the frame, it requests the set of foci needed by the frame:
  164.                     
  165. ODBoolean ClockTimeFrame::HandleMouseDown(Environment* ev, ODFacet* facet, ODEventData* event)
  166. {
  167.     ODBoolean wasHandled = kODFalse;
  168.  
  169.     if (!facet->GetWindow(ev)->IsActive(ev))
  170.         facet->GetWindow(ev)->Select(ev);
  171.     else
  172.     {
  173.         this->Activate(ev);
  174.     }
  175.  
  176.     return wasHandled;
  177. }
  178.  
  179. void ClockFrame::Activate(Environment* ev)
  180. {
  181.     if (!fHasFoci)
  182.     {
  183.         ODBoolean succeeded = kODFalse;
  184.                             
  185.         succeeded = fArbitrator->RequestFocusSet(ev, fFocusSet,fFrame);
  186.                 
  187.         if (succeeded)
  188.         {
  189.             fHasFoci = kODTrue;
  190.             fNeedsFoci = kODFalse;
  191.             this->AcquiredRequestedFoci(ev);
  192.         }
  193.     }
  194. }
  195.  
  196. void ClockTimeFrame::AcquiredRequestedFoci(Environment* ev)
  197. {
  198.     this->AcquiredSelectionFocus(ev);
  199.     fClockPart->InstallMenus(ev);
  200. }
  201.  
  202. void ClockFrame::AcquiredSelectionFocus(Environment* ev)
  203. {
  204.     gClockGlobals->AcquiringFocus(ev, fFrame);
  205.     gClockGlobals->ResumeWindows(ev, kODFalse);
  206. }
  207.  
  208. Relinquishing Foci
  209.  
  210. A part will usually relinquish a focus when another part asks for it via BeginRelinquishFocus(). In this case, the part simply returns kODTrue in BeginRelinquishFocus(), and removes menus, palettes, blinking cursors and so forth in CommitRelinquishFocus().
  211.  
  212. Most parts will willingly relinquish the common foci when asked, with the exception of the modal focus. The code below is somewhat simplified. The part may in fact wish to relinquish the modal focus to another of its own frames in cases where a nested modal dialog is being displayed from within the frame that owns the modal focus.
  213.  
  214. ODBoolean ClockTimeFrame::BeginRelinquishFocus(Environment* ev, 
  215.                             ODTypeToken focus,
  216.                             ODFrame* proposedFrame)
  217. {
  218.     if (focus == fModalFocus)
  219.         return kODFalse;
  220.     else
  221.         return kODTrue;
  222. }
  223.  
  224. void  ClockTimeFrame::CommitRelinquishFocus(Environment *ev,
  225.                 ODTypeToken focus,
  226.                 ODFrame* proposedFrame)
  227. {
  228.     // Hide palettes and modeless dialogs
  229.     // remove blinking text insertion point
  230.     // etc
  231. }
  232.  
  233.  
  234. If a part does anything more than return kODTrue or kODFalse in BeginRelinquishFocus(), it will have to undo the effects in AbortRelinquishFocus().
  235.  
  236. A part must also relinquish foci when a frame is going away. In the methods delegated to by Part::DisplayFrameClosed() and Part:: DisplayFrameRemoved(), or when the last facet is removed:
  237.  
  238.     fArbitrator->RelinquishFocusSet(ev, fFocusSet, frame);
  239.  
  240. A part may also wish to ensure that it keeps various foci together, so it may wish to relinquish a set if it is notified via FocusLost() that it lost a particular focus.
  241.  
  242. Window Activation and Frame Activation
  243.  
  244. Not all platforms have a notion of active windows, but for those that do, following these recipes results in sensible behavior in several cases:
  245.  
  246. A frame with the selection focus can record this fact  when its window is deactivated, and preserve a background selection. When the window is reactivated (eg. by clicking on its title bar), the formerly active frame regains the selection focus. When the user clicks in the content area of an inactive window, the part which receives the event will activate the window, unless the click is in a background selection, in which case the part might allow the selection to be dragged without activating the window. In this case, the destination window will be activated when the drop is completed.
  247.  
  248. When a new document is opened from stationery, the menus of the root part will appear. The same will be true when the user opens an existing document. It is not necessarily recommended, but an embedded part can choose to record its active state when saved and attempt to restore it when the document is opened. And the part at the root can choose to allow this, or always ensure that it is active  itself. 
  249.  
  250. As shown in the following recipes, the behavior described above is achieved by saving the state of a frame when a window deactivate event is received, and restoring it when the window is activated. The frame object attached to the part info makes use of two Boolean properties fNeedsFoci and fHasFoci.
  251.  
  252. The basic recipe is fairly simple. The frame handler object is attached to the part info in Part::DisplayFrameAdded() and and Part::DisplayFrameConnected().  The fNeedsFoci flag is set to true if the frame is at the root. The flag is also set to true or false when a window is deactivated, depending on whether or not the frame is active. The fNeedsFoci flag is checked when the part receives a window activate event, and if it is true, the frame is activated.
  253.  
  254. Creating a New Window
  255.  
  256. The first time a window, and hence a root frame, is created, the part needs to ensure that the root frame is active when the window becomes active. The frame handler object will be created and attached in Part::DisplayFrameAdded():
  257.  
  258. void ClockPart::DisplayFrameAdded(Environment *ev,
  259.                                   ODFrame* frame)
  260. {
  261.     // We check for an existing ClockFrame in Part Info, because DisplayFrameAdded
  262.     // can get called multiple times, eg. when changing parts. 
  263.     // However DisplayFrameRemoved should probably delete the Part Info
  264.     
  265.     ClockFrame* clockFrame = (ClockFrame*) frame->GetPartInfo(ev);
  266.     if (clockFrame == kODNULL)
  267.     {
  268.         ODTypeToken presentation = this->CheckPresentation(ev, frame);
  269.         clockFrame = this->CreateClockFrame(ev, frame, presentation);
  270.         frame->SetPartInfo(ev, (ODInfoType) clockFrame);            
  271.     }
  272.     if (clockFrame)
  273.     {
  274.         clockFrame->SetPart(ev, this);
  275.         clockFrame->Add(ev);
  276.     }
  277. }
  278.  
  279. ODTypeToken ClockPart::CheckPresentation(Environment* ev, ODFrame* frame)
  280. {
  281.     ODTypeToken presentation = frame->GetPresentation(ev);
  282.     if ((presentation != fTimePresentation)
  283.         && (presentation != fSynchronizePresentation)
  284.         && (presentation != fAlarmSettingsPresentation)
  285.         && (presentation != fDisplaySettingsPresentation)
  286.         && (presentation != fDialogPresentation))
  287.     {
  288.         presentation = fTimePresentation;
  289.         frame->SetPresentation(ev, presentation);
  290.     }
  291.     return presentation;
  292. }
  293.  
  294. ClockFrame* ClockPart::CreateClockFrame(Environment* ev, ODFrame* frame, ODTypeToken presentation)
  295. {
  296.     ClockFrame* clockFrame = kODNULL;
  297.     if (presentation == fTimePresentation)
  298.     {
  299.         ClockTimeFrame* timeFrame = new ClockTimeFrame();
  300.         timeFrame->InitClockTimeFrame(ev, fSession, frame, this);
  301.         clockFrame = timeFrame;
  302.     }
  303.     else if (presentation == fSynchronizePresentation)
  304.     {
  305.         ClockSynchroDialogFrame* dialogFrame = new ClockSynchroDialogFrame();
  306.         dialogFrame->InitClockSynchroDialogFrame(ev, fSession, frame, this);
  307.         clockFrame = dialogFrame;
  308.     }
  309.     else if (presentation == fAlarmSettingsPresentation)
  310.     {
  311.         ClockAlarmSettingsDialogFrame* dialogFrame = new ClockAlarmSettingsDialogFrame();
  312.         dialogFrame->InitClockAlarmSettingsDialogFrame(ev, fSession, frame, this);
  313.         clockFrame = dialogFrame;
  314.     }
  315.     else if (presentation == fDisplaySettingsPresentation)
  316.     {
  317.         ClockDisplaySettingsDialogFrame* dialogFrame = new ClockDisplaySettingsDialogFrame();
  318.         dialogFrame->InitClockDisplaySettingsDialogFrame(ev, fSession, frame, this);
  319.         clockFrame = dialogFrame;
  320.         clockFrame->SetShouldDeleteWindow(kODFalse);
  321.     }
  322.     else if (presentation == fDialogPresentation)
  323.     {
  324.         ClockDialogFrame* dialogFrame = new ClockDialogFrame();
  325.         dialogFrame->InitClockDialogFrame(ev, fSession, frame, this);
  326.         clockFrame = dialogFrame;
  327.         clockFrame->SetShouldDeleteWindow(kODFalse);
  328.     }
  329.     return clockFrame;
  330. }
  331.  
  332. The initialization method of the frame handler sets fNeedsFoci to kODTrue if the frame is at the root:
  333.  
  334. void ClockTimeFrame::InitClockTimeFrame(Environment* ev, 
  335.                                         ODSession* session,
  336.                                         ODFrame* frame, 
  337.                                         ClockPart* clockPart)
  338. {
  339.     this->InitClockFrame(ev, session, frame, clockPart);
  340.     fCrossCursor = **GetCursor(crossCursor);    
  341.  
  342.     fFocusSet->Add(ev, fKeyFocus);
  343.     fFocusSet->Add(ev, fMenuFocus);
  344.     fFocusSet->Add(ev, fSelectionFocus);
  345.  
  346.     if (frame->IsRoot(ev))
  347.         fNeedsFoci = kODTrue;
  348. }
  349.  
  350. Saving and Restoring Windows
  351.  
  352. When a window is saved in a document, and the document is reopened, the part's DisplayFrameConnected() method is called, and the frame handler object will be attached there.
  353.     
  354. When a window is activated or deactivated
  355.  
  356. When a window is first created or is brought to the front, each part will receive an activate event for each facet it has in that window. If the user clicks in the title bar of an inactive window, the window will be brought to the front and each part in the previously active window will get a deactivate event for each facet it has in that window. If a frame has foci, it sets “fNeedsFoci” to true, so that the next time that window is activated, the part can reclaim the foci.
  357.  
  358. When a deactivate event is received:
  359.  
  360. void ClockFrame::DeactivatingWindow(Environment* ev, ODFacet* facet, ODEventData* event)
  361. {
  362.     if (fHasFoci) // (fFrame == fArbitrator->AcquireFocusOwner(ev, fSelectionFocus))
  363.     {
  364.         fNeedsFoci = kODTrue;
  365.     }
  366.     else
  367.         fNeedsFoci = kODFalse;
  368. }
  369.  
  370.  
  371. When an activate event is received:
  372.  
  373. void ClockFrame::ActivatingWindow(Environment* ev, ODFacet* facet, ODEventData* event) 
  374. {
  375.  
  376.     if (fNeedsFoci)
  377.     {
  378.         this->Activate(ev);
  379.     }
  380.     fNeedsFoci = kODFalse;
  381. }
  382.  
  383.  
  384. When a different frame in the same window is activated
  385.  
  386. When the user clicks in a different frame in the same window, one part editor will request foci and install menus and other interface elements.  As described earlier, this kind of activation is done in several places (mouse down, mouse up, window activate) , so it's a good idea to record that a frame has the selection focus, so that the method which activates a frame can exit quickly if the frame is already active. After RequestFocusSet() succeeds (eg. when handling mouse down and mouse up), fNeedsFoci and fHasFoci are updated:
  387.  
  388. void ClockFrame::Activate(Environment* ev)
  389. {
  390.     if (!fHasFoci)
  391.     {
  392.         ODBoolean succeeded = kODFalse;
  393.                             
  394.         succeeded = fArbitrator->RequestFocusSet(ev, fFocusSet,fFrame);
  395.                 
  396.         if (succeeded)
  397.         {
  398.             fHasFoci = kODTrue;
  399.             fNeedsFoci = kODFalse;
  400.             this->AcquiredRequestedFoci(ev);
  401.         }
  402.     }
  403. }
  404.  
  405. The previously active part will give up foci, and should record that. 
  406.  
  407. void ClockFrame::CommitRelinquishFocus(Environment *ev,
  408.                                          ODTypeToken focus,
  409.                                          ODFrame* proposedFrame)
  410. {
  411.     this->FocusLost(ev, focus, proposedFrame);
  412. }
  413.  
  414. void ClockFrame::FocusLost(Environment *ev,
  415.                  ODTypeToken    focus, ODFrame* proposedFrame)
  416. {
  417.     if ( (focus == fSelectionFocus) ||
  418.         (focus == fMenuFocus) ||
  419.         (focus == fKeyFocus) )
  420.     {
  421.         fHasFoci = kODFalse;
  422.     }    
  423.         
  424.     if (focus == fSelectionFocus) 
  425.     {    
  426.         this->LostSelectionFocus(ev, proposedFrame);
  427.  
  428.     }
  429. }    
  430.  
  431.  
  432. Saving An Embedded Selection
  433.  
  434. If a part does wish to save its selection persistently even when it is embedded, it can do the following.  
  435.  
  436. Note : The Human Interface guidelines do not recommend this. Note also that the root part must cooperate by not activating itself if a part already has the selection focus, as shown below.
  437.  
  438. In Part::WritePartInfo():
  439.  
  440.     if (partInfo)
  441.     {
  442.         ODBoolean needsActivating = ((ClockFrame*)partInfo)->fNeedsFoci 
  443.                                      || ((ClockFrame*)partInfo)->fHasFoci;
  444.         StorageUnitSetValue(storageUnitView, ev, sizeof(ODBoolean),
  445.                                     (ODValue)&needsActivating);
  446.     }
  447.  
  448.  
  449. In Part::ReadPartInfo():
  450.  
  451. ODInfoType ClockPart::ReadPartInfo(Environment *ev,
  452.                                 ODFrame* frame, 
  453.                                 ODStorageUnitView*    storageUnitView)
  454. {
  455.     if (storageUnitView->GetSize(ev))
  456.     {
  457.         ClockTimeFrame* partInfo = new ClockTimeFrame();
  458.         partInfo->InitClockTimeFrame(ev, fSession, frame, this);
  459.         
  460.         
  461.         ODBoolean needsActivating;
  462.         StorageUnitGetValue(storageUnitView, ev, sizeof(ODBoolean),
  463.                                     (ODValue)&(needsActivating));
  464.         partInfo->SetNeedsActivating(needsActivating);
  465.                                         
  466.         return (ODInfoType)partInfo;
  467.     }
  468.     else
  469.         return ((ODInfoType)kODNULL);
  470. }
  471.  
  472.  
  473. When root part handles a window activate event:
  474.  
  475.     if (fNeedsFoci)
  476.     {
  477.         ODFrame* selectionFrame 
  478.             = fSession->GetArbitrator(ev)->AcquireFocusOwner(ev, fSelectionFocus);
  479.         if (!selectionFrame)
  480.         {
  481.             // activate
  482.         }
  483.         ODReleaseObject(ev, selectionFrame);
  484.     }
  485.  
  486.  
  487. This works because activate events are sent bottom-up to the facets in the window. Note also that the part externalizing the flag must mark the draft as changed when it acquires the selection focus, so that Save is enabled. 
  488.  
  489.